#define NAPI_UTILS_ENABLE_JSON

#ifndef NAPI_UTILS_H
#define NAPI_UTILS_H

#include <cstdint>
#include <string>
#include <vector>

#include "google/protobuf/message_lite.h"
#include "google/protobuf/repeated_field.h"
#include "google/protobuf/repeated_ptr_field.h"
#include "hilog/log.h"
#include "napi/native_api.h"

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 自定义业务域
#define LOG_TAG "PbCpp"   // 日志标签

#ifdef NDEBUG
#undef OH_LOG_DEBUG
#undef OH_LOG_INFO
#undef OH_LOG_WARN
#define OH_LOG_DEBUG(...) ((void)0)
#define OH_LOG_INFO(...) ((void)0)
#define OH_LOG_WARN(...) ((void)0)
#endif

namespace NapiUtils {
extern std::atomic_bool g_ENABLE_ARENA;
napi_value SetArenaEnabled(napi_env env, napi_callback_info info);
#ifdef NAPI_UTILS_ENABLE_JSON
napi_value ToJsonString(napi_env env, napi_callback_info info);
#endif
void IncArenaRefCount(const google::protobuf::Arena *arena);
void DecArenaRefCount(const google::protobuf::Arena *arena);
inline google::protobuf::Arena *NewArena() {
    auto arena = new google::protobuf::Arena();
    OH_LOG_INFO(LOG_APP, "[NU] New arena: 0x%{public}lx", reinterpret_cast<uintptr_t>(arena));
    IncArenaRefCount(arena);
    return arena;
}
napi_value GetThis(napi_env env, napi_callback_info info);
std::vector<napi_value> GetArgs(napi_env env, napi_callback_info info, size_t argc);
std::tuple<napi_value, std::vector<napi_value>> GetThisAndArgs(napi_env env, napi_callback_info info, size_t argc);
napi_value CreateUndefined(napi_env env);
napi_value CreateNull(napi_env env);
bool IsNull(napi_env env, napi_value value);
bool IsUndefined(napi_env env, napi_value value);
std::optional<napi_value> GetProperty(napi_env env, napi_value obj, const std::string &name);
void SetNamespaceObj(const std::string &ns,  const std::string &newNsObjName, const napi_value newNsObj);
napi_value GetNamespaceObj(napi_env env, napi_value exports, const std::string &ns);
template <typename T>
napi_value CreateScalarValue(napi_env env, T value) {
    napi_value result = nullptr;
    if constexpr (std::is_same_v<T, int32_t>) {
        if (napi_create_int32(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to create int32 from %{public}d", value);
            return CreateUndefined(env);
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Created int32 from %{public}d", value);
        }
    } else if constexpr (std::is_same_v<T, uint32_t>) {
        if (napi_create_uint32(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to create uint32 from %{public}u", value);
            return CreateUndefined(env);
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Created uint32 from %{public}u", value);
        }
    } else if constexpr (std::is_same_v<T, int64_t>) {
        if (napi_create_int64(env, value, &result) != napi_ok && // 先尝试创建 number，再尝试创建 bigint
            napi_create_bigint_int64(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to create int64 from %{public}lld", value);
            return CreateUndefined(env);
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Created int64 from %{public}lld", value);
        }
    } else if constexpr (std::is_same_v<T, uint64_t>) {
        if (napi_create_bigint_uint64(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to create uint64 from %{public}lu", value);
            return CreateUndefined(env);
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Created uint64 from %{public}lu", value);
        }
    } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
        if (napi_create_double(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to create double from %{public}f", value);
            return CreateUndefined(env);
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Created double from %{public}f", value);
        }
    } else if constexpr (std::is_same_v<T, bool>) {
        if (napi_get_boolean(env, value ? 1 : 0, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to create boolean from from %{public}s", value ? "true" : "false");
            return CreateUndefined(env);
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Created bool from %{public}s", value ? "true" : "false");
        }
    } else if constexpr (std::is_same_v<T, std::string>) {
        if (napi_create_string_utf8(env, value.c_str(), value.size(), &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to create string from %{public}s", value.c_str());
            return CreateUndefined(env);
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Created string from %{public}s", value.c_str());
        }
    } else {
        OH_LOG_ERROR(LOG_APP, "[NU] Unsupported type for primitive value");
        return CreateUndefined(env);
    }
    return result;
}
napi_value CreateUInt8Array(napi_env env, const char *data, size_t length);
napi_value CreateArray(napi_env env, const std::vector<napi_value> &elements);
bool GetInt64FromJsLong(napi_env env, napi_value value, int64_t *result);
template <typename T>
T GetScalarValue(napi_env env, napi_value value) {
    T result;
    if constexpr (std::is_same_v<T, int32_t>) {
        if (napi_get_value_int32(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to extract int32");
            return 0;
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Extracted int32 %{public}d", result);
        }
    } else if constexpr (std::is_same_v<T, uint32_t>) {
        if (napi_get_value_uint32(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to extract uint32");
            return 0;
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Extracted uint32 %{public}u", result);
        }
    } else if constexpr (std::is_same_v<T, int64_t>) {
        bool lossless = true;
        if (napi_get_value_int64(env, value, &result) != napi_ok && // 先尝试直接读取 int64，再尝试从 bigint 中读取 int64
            napi_get_value_bigint_int64(env, value, &result, &lossless) != napi_ok &&
            !GetInt64FromJsLong(env, value, &result)) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to extract int64");
            return 0;
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Extracted int64 %{public}lld", result);
        }
    } else if constexpr (std::is_same_v<T, uint64_t>) {
        bool lossless = true;
        if (napi_get_value_bigint_uint64(env, value, &result, &lossless) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to extract uint64");
            return 0;
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Extracted uint64 %{public}lu", result);
        }
    } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
        double temp = 0.0;
        if (napi_get_value_double(env, value, &temp) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to extract double");
            return 0.0;
        } else {
            result = static_cast<T>(temp);
            OH_LOG_INFO(LOG_APP, "[NU] Extracted float/double %{public}f", result);
        }
    } else if constexpr (std::is_same_v<T, bool>) {
        if (napi_get_value_bool(env, value, &result) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to extract boolean");
            return false;
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Extracted bool %{public}s", result ? "true" : "false");
        }
    } else if constexpr (std::is_same_v<T, std::string>) {
        size_t length;
        if (napi_get_value_string_utf8(env, value, nullptr, 0, &length) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to get string length");
            return T();
        }
        std::string str(length, '\0');
        if (napi_get_value_string_utf8(env, value, &str[0], length + 1, &length) != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to extract string");
            return T();
        } else {
            result = str;
            OH_LOG_INFO(LOG_APP, "[NU] Extracted string %{public}s", result.c_str());
        }
    } else {
        OH_LOG_ERROR(LOG_APP, "[NU] Unsupported type for extraction");
        return T();
    }
    return result;
}
std::string GetUInt8ArrayValue(napi_env env, napi_value value);
napi_value NewInstance(napi_env env, napi_ref ctorRef);
napi_value FromBinary(napi_env env, napi_callback_info info, google::protobuf::MessageLite *impl, napi_ref ctorRef,
                      napi_finalize finalizer);
template <typename T>
napi_value ToBinary(napi_env env, napi_callback_info info) {
    static_assert(std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T must inherit from google::protobuf::MessageLite");
    auto thisArg = NapiUtils::GetThis(env, info);
    T *impl = nullptr;
    napi_unwrap(env, thisArg, reinterpret_cast<void **>(&impl));
    auto bin = impl->SerializeAsString();
    return NapiUtils::CreateUInt8Array(env, bin.data(), bin.size());
}
template <typename T, typename Stub>
napi_value Clone(napi_env env, napi_callback_info info, napi_ref ctorRef, napi_finalize finalizer) {
    static_assert(std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T must inherit from google::protobuf::MessageLite");
    T *nImpl = nullptr;
    if (NapiUtils::g_ENABLE_ARENA) {
        auto arena = NapiUtils::NewArena();
        nImpl = google::protobuf::Arena::Create<T>(arena);
    } else {
        nImpl = new T();
    }
    auto thisArg = NapiUtils::GetThis(env, info);
    T *impl = nullptr;
    if (napi_unwrap(env, thisArg, reinterpret_cast<void **>(&impl)) == napi_ok && impl) {
        nImpl->MergeFrom(*impl);
    } else {
        Stub::InitByNapiObj(env, thisArg, impl);
    }

    auto newInstance = NewInstance(env, ctorRef);
    napi_wrap(env, newInstance, nImpl, finalizer, nullptr, nullptr);;
    return newInstance;
}
template <typename T>
T *Unwrap(napi_env env, napi_value arg, bool error = true) {
    static_assert(std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T must inherit from google::protobuf::MessageLite");
    T *impl = nullptr;
    if (napi_unwrap(env, arg, reinterpret_cast<void **>(&impl)) != napi_ok || !impl) {
        if (error) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to unwrap from napi_value");
        } else {
            OH_LOG_INFO(LOG_APP, "[NU] Failed to unwrap from napi_value");
        }
    }
    return impl;
}
template <typename T, typename Stub>
napi_value Wrap(napi_env env, T *cppValue) {
    static_assert(std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T must inherit from google::protobuf::MessageLite");
    napi_value ctor = nullptr;
    napi_value ins = nullptr;
    napi_get_reference_value(env, Stub::ctorRef, &ctor);
    napi_new_instance(env, ctor, 0, nullptr, &ins);
    if (g_ENABLE_ARENA) {
        IncArenaRefCount(cppValue->GetArena());
        napi_wrap(env, ins, cppValue, Stub::Finalizer, nullptr, nullptr);
    } else {
        T *copy = new T();
        copy->CopyFrom(*cppValue);
        napi_wrap(env, ins, copy, Stub::Finalizer, nullptr, nullptr);
    }
    return ins;
}
napi_value CreateArrayWithValues(napi_env env, const google::protobuf::RepeatedPtrField<std::string> &elements,
                                 bool isBytes = false);
template <typename T, typename Stub>
napi_value CreateArrayWithValues(napi_env env, const google::protobuf::RepeatedPtrField<T> &elements) {
    static_assert(std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T must inherit from google::protobuf::MessageLite");
    napi_value result = nullptr;
    napi_status status = napi_create_array_with_length(env, elements.size(), &result);
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "[NU] Failed to create array from elements");
        return CreateUndefined(env);
    }
    for (size_t i = 0; i < elements.size(); ++i) {
        status = napi_set_element(env, result, i, NapiUtils::Wrap<T, Stub>(env, const_cast<T *>(&elements[i])));
        if (status != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to set element %{public}lu in array", i);
            return CreateUndefined(env);
        }
    }
    return result;
}
template <typename T>
napi_value CreateArrayWithValues(napi_env env, const google::protobuf::RepeatedField<T> &elements) {
    static_assert(!std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T mustn't inherit from google::protobuf::MessageLite");
    napi_value result = nullptr;
    napi_status status = napi_create_array_with_length(env, elements.size(), &result);
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "[NU] Failed to create array from elements");
        return CreateUndefined(env);
    }
    for (size_t i = 0; i < elements.size(); ++i) {
        status = napi_set_element(env, result, i, NapiUtils::CreateScalarValue<T>(env, elements[i]));
        if (status != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to set element %{public}lu in array", i);
            return CreateUndefined(env);
        }
    }
    return result;
}
void SetFromArrayValue(napi_env env, google::protobuf::RepeatedPtrField<std::string> &repeated, napi_value array,
                       bool isBytes = false);
template <typename T, typename Stub>
void SetFromArrayValue(napi_env env, google::protobuf::RepeatedPtrField<T> &repeated, napi_value array) {
    static_assert(std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T must inherit from google::protobuf::MessageLite");
    repeated.Clear();
    uint32_t length = 0;
    napi_get_array_length(env, array, &length);
    for (uint32_t i = 0; i < length; ++i) {
        napi_value element = nullptr;
        if (napi_get_element(env, array, i, &element) == napi_ok) {
            Stub::InitByNapiObj(env, element, repeated.Add());
        } else {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to get %{public}u-th element", i);
        }
    }
}
template <typename T>
void SetFromArrayValue(napi_env env, google::protobuf::RepeatedField<T> &repeated, napi_value array) {
    static_assert(!std::is_base_of_v<google::protobuf::MessageLite, T>,
                  "T mustn't inherit from google::protobuf::MessageLite");
    repeated.Clear();
    uint32_t length = 0;
    napi_get_array_length(env, array, &length);
    repeated.Reserve(length);
    for (uint32_t i = 0; i < length; ++i) {
        napi_value element = nullptr;
        napi_get_element(env, array, i, &element);
        repeated.Add(NapiUtils::GetScalarValue<T>(env, element));
    }
}

template <typename ValueStubType, typename ProtoMapType>
napi_value CreateMapWithValues(napi_env env, const ProtoMapType &protoMap) {
    napi_value result = nullptr;
    napi_status status = napi_create_object(env, &result);
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "[NU] Failed to create proto map object");
        return CreateUndefined(env);
    }

    for (const auto &pair : protoMap) {
        napi_value key = nullptr;
        napi_value value = nullptr;

        using KeyType = typename ProtoMapType::key_type;
        using ValueType = typename ProtoMapType::mapped_type;

        // 转换key - 支持protobuf所有有效的key类型
        if constexpr (std::is_same_v<KeyType, std::string>) {
            key = CreateScalarValue<std::string>(env, pair.first);
        } else if constexpr (std::is_integral_v<KeyType>) {
            key = CreateScalarValue<KeyType>(env, pair.first);
        } else {
            OH_LOG_ERROR(LOG_APP, "[NU] Unsupported map key type");
            continue;
        }

        // 转换value - 支持除map外的所有类型
        if constexpr (std::is_arithmetic_v<ValueType> || std::is_same_v<ValueType, std::string> ||
                      std::is_same_v<ValueType, bool>) {
            value = CreateScalarValue<ValueType>(env, pair.second);
        } else if constexpr (std::is_base_of_v<google::protobuf::MessageLite, ValueType>) {
            // 对于消息类型，需要特殊处理
            // 这里假设每个消息类型都有对应的Stub类
            value = Wrap<ValueType, ValueStubType>(env, const_cast<ValueType *>(&pair.second));
        } else if constexpr (std::is_enum_v<ValueType>) {
            value = CreateScalarValue<int32_t>(env, static_cast<int32_t>(pair.second));
        } else {
            OH_LOG_ERROR(LOG_APP, "[NU] Unsupported map value type");
            continue;
        }

        if (key != nullptr && value != nullptr) {
            if constexpr (std::is_same_v<KeyType, std::string>) {
                status = napi_set_named_property(env, result, pair.first.c_str(), value);
            } else {
                // 数字key转为字符串属性名
                std::string keyStr = std::to_string(pair.first);
                status = napi_set_named_property(env, result, keyStr.c_str(), value);
            }

            if (status != napi_ok) {
                OH_LOG_ERROR(LOG_APP, "[NU] Failed to set proto map property");
            }
        }
    }

    return result;
}

template <typename MapFieldType>
void SetFromMapValue(napi_env env, MapFieldType &mapField, napi_value napiMap) {
    using KeyType = typename MapFieldType::key_type;
    using ValueType = typename MapFieldType::mapped_type;

    // 验证输入是对象
    napi_valuetype valueType;
    napi_status status = napi_typeof(env, napiMap, &valueType);
    if (status != napi_ok || valueType != napi_object) {
        OH_LOG_ERROR(LOG_APP, "[NU] Expected object for map field");
        return;
    }

    // 清空现有map
    mapField.clear();

    // 获取属性名
    napi_value propertyNames = nullptr;
    status = napi_get_property_names(env, napiMap, &propertyNames);
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "[NU] Failed to get property names");
        return;
    }

    uint32_t length = 0;
    status = napi_get_array_length(env, propertyNames, &length);
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "[NU] Failed to get property names length");
        return;
    }

    // 遍历属性
    for (uint32_t i = 0; i < length; i++) {
        napi_value propertyName = nullptr;
        status = napi_get_element(env, propertyNames, i, &propertyName);
        if (status != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to get property name");
            continue;
        }

        napi_value propertyValue = nullptr;
        status = napi_get_property(env, napiMap, propertyName, &propertyValue);
        if (status != napi_ok) {
            OH_LOG_ERROR(LOG_APP, "[NU] Failed to get property value");
            continue;
        }

        // 转换key
        KeyType key;
        if constexpr (std::is_same_v<KeyType, std::string>) {
            key = GetScalarValue<std::string>(env, propertyName);
        } else if constexpr (std::is_integral_v<KeyType>) {
            std::string keyStr = GetScalarValue<std::string>(env, propertyName);
            if constexpr (std::is_signed_v<KeyType>) {
                key = static_cast<KeyType>(std::stoll(keyStr));
            } else {
                key = static_cast<KeyType>(std::stoull(keyStr));
            }
        } else {
            OH_LOG_ERROR(LOG_APP, "[NU] Unsupported map key type");
            continue;
        }

        // 转换value
        ValueType val;
        if constexpr (std::is_arithmetic_v<ValueType> || std::is_same_v<ValueType, std::string> ||
                      std::is_same_v<ValueType, bool>) {
            val = GetScalarValue<ValueType>(env, propertyValue);
        } else if constexpr (std::is_base_of_v<google::protobuf::MessageLite, ValueType>) {
            ValueType *unwrapped = NapiUtils::Unwrap<ValueType>(env, propertyValue);
            if (unwrapped) {
                mapField[key].CopyFrom(*unwrapped); // TODO: 考虑将Add出来的新实例替换ArkTS对象中wrap的C++对象
            }
            continue;
        } else if constexpr (std::is_enum_v<ValueType>) {
            int32_t enumValue = GetScalarValue<int32_t>(env, propertyValue);
            val = static_cast<ValueType>(enumValue);
        } else {
            OH_LOG_ERROR(LOG_APP, "[NU] Unsupported map value type");
            continue;
        }

        // 设置到protobuf map中
        mapField[key] = std::move(val);
    }
}

} // namespace NapiUtils

#endif // NAPI_UTILS_H
